﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using OS.Common.Core;
using OS.Common.Enums;
using OS.Common.Extention.DTO;
using OS.Orm.Core;
using OS.Orm.Enums;
using OS.Orm.Models;
using OS.Orm.Utils;

namespace OS.Orm.Extention
{
    /// <summary>
    /// 连接池扩展
    /// </summary>
    public static class ConnoctionExtention
    {
        #region 插入扩展

        /// <summary>
        /// 插入新记录
        /// </summary>
        /// <typeparam name="TType">动态类型实体</typeparam>
        /// <param name="dbConnection">数据库连接</param>
        /// <param name="model">对应实体</param>
        /// <param name="tableName">表名，如果为空则以TType.GetType().Name</param>
        /// <param name="isIdAuto">[Id]主键是否是自动增长，如果是同步返回，不是则需要外部传值（不通过Attribute处理是为了尽可能减少依赖）</param>
        /// <returns></returns>
        public static async Task<CustomResultId> Insert<TType>(this IDbConnection dbConnection, TType model,
            string tableName = null, bool isIdAuto = true)
        {
            if (string.IsNullOrEmpty(tableName))
                tableName = model.GetType().Name;

            var key = string.Concat(tableName, "|", isIdAuto, "|", dbConnection.ConnectionString, "|",
                typeof(TType).FullName);
            var ormInfo = GetInsertOrmCacheInfo<TType>(tableName, key);

            var parameter = new DynamicParameters(ormInfo.ParameterFunc?.Invoke(model));

            long id;
            if (isIdAuto)
                id = await dbConnection.ExecuteScalarAsync<long>(ormInfo.Sql, parameter);
            else
                id = await dbConnection.ExecuteAsync(ormInfo.Sql, parameter);

            return id > 0 ? new CustomResultId(id) : new CustomResultId(ResultType.AddFail, "添加操作失败！");
        }

        /// <summary>
        /// 获取orm插入sql缓存语句
        /// </summary>
        /// <typeparam name="TType">动态类型</typeparam>
        /// <param name="tableName">表名</param>
        /// <param name="key">key键</param>
        /// <returns></returns>
        private static OrmOperateInfo GetInsertOrmCacheInfo<TType>(string tableName, string key)
        {
            var cache = OrmCacheUtil.GetCacheInfo(key);
            if (cache != null) return cache;

            cache = new OrmOperateInfo
            {
                Sql = GetInsertSql<TType>(tableName, out List<PropertyInfo> propertyInfoList),
                ParameterFunc = SqlParameterEmit.CreateDictionaryDeleMethod<TType>(propertyInfoList)
            };
            OrmCacheUtil.AddCacheInfo(key, cache);
            return cache;
        }

        /// <summary>
        /// 获取插入sql语句
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="tableName">表名</param>
        /// <param name="propertyInfoList">属性集合</param>
        /// <returns></returns>
        private static string GetInsertSql<TType>(string tableName, out List<PropertyInfo> propertyInfoList)
        {
            var sqlCols = new StringBuilder("INSERT INTO ");
            sqlCols.Append(tableName).Append(" (");

            var sqlValues = new StringBuilder(" VALUES (");
            var properties = typeof(TType).GetProperties();
            propertyInfoList = new List<PropertyInfo>(properties.Length);

            bool isStart = false, haveAuto = false;
            foreach (var propertyInfo in properties)
            {
                var isAuto = propertyInfo.GetCustomAttribute<AutoColumnAttribute>() != null;
                if (isAuto)
                {
                    haveAuto = true;
                    continue;
                }

                if (isStart)
                {
                    sqlCols.Append(",");
                    sqlValues.Append(",");
                }
                else isStart = true;

                sqlCols.Append("`").Append(propertyInfo.Name).Append("`");
                sqlValues.Append("@").Append(propertyInfo.Name);
                propertyInfoList.Add(propertyInfo);
            }

            sqlCols.Append(")");
            sqlValues.Append(")");
            sqlCols.Append(sqlValues);

            if (haveAuto) sqlCols.Append(";SELECT LAST_INSERT_ID();");
            return sqlCols.ToString();
        }

        #endregion

        #region 全量更新

        /// <summary>
        /// 全量更新
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="dbConnection">数据库连接</param>
        /// <param name="model">实体类型</param>
        /// <param name="where">更新条件，为空则默认使用Id</param>
        /// <param name="tableName">表名</param>
        /// <returns></returns>
        internal static async Task<CustomResult> UpdateAll<TType>(this IDbConnection dbConnection, TType model,
            Expression<Func<TType, bool>> where = null, string tableName = null)
        {
            if (string.IsNullOrEmpty(tableName))
                tableName = typeof(TType).Name;

            var visitor = new SqlExpressionVisitor();
            var sqlString = new StringBuilder("UPDATE ");

            var whereSql = VisitWhereExpress(visitor, where); //放在前面获取where后面的成员参数，update时移除
            GetUpdateAllSql<TType>(tableName, sqlString, visitor);
            sqlString.Append(whereSql);  //where语句追加在后边

            return await ExecuteUpdate(dbConnection, model, tableName, sqlString, visitor);
        }

        private static void GetUpdateAllSql<TType>(string tableName, StringBuilder sqlString,
            SqlExpressionVisitor visitor)
        {
            sqlString.Append(tableName).Append(" SET ");
            var properties = typeof(TType).GetProperties();

            var isStart = false;
            foreach (var propertyInfo in properties)
            {
                if (visitor.Properties.ContainsKey(propertyInfo.Name)
                    || propertyInfo.GetCustomAttribute<AutoColumnAttribute>() != null)
                    continue;

                sqlString.Append(isStart ? "," : string.Empty).Append("`").Append(propertyInfo.Name).Append("`")
                    .Append("=").Append("@").Append(propertyInfo.Name);

                if (!isStart)
                    isStart = true;

                visitor.Properties.Add(propertyInfo.Name, propertyInfo);
            }
        }

        #endregion

        /// <summary>
        /// 实体更新
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="dbConnection">数据库连接</param>
        /// <param name="model">实体类型</param>
        /// <param name="update">更新动态字段</param>
        /// <param name="tableName">表名</param>
        /// <param name="where">更新动态条件</param>
        /// <returns></returns>
        internal static async Task<CustomResult> UpdatePartail<TType>(this IDbConnection dbConnection, TType model,
            Expression<Func<TType, object>> update, string tableName = null, Expression<Func<TType, bool>> where = null)
        {
            if (string.IsNullOrEmpty(tableName)) tableName = typeof(TType).Name;

            var sqlBuilder = new StringBuilder();
            var visitor = new SqlExpressionVisitor();
            GetUpdateExpressionSql(update, where, tableName, visitor, sqlBuilder);

            return await ExecuteUpdate(dbConnection, model, tableName, sqlBuilder, visitor);
        }

        /// <summary>
        /// 执行修改语句
        /// </summary>
        /// <typeparam name="TType">动态类型实体</typeparam>
        /// <param name="dbConnection">数据库链接</param>
        /// <param name="model">实体类型</param>
        /// <param name="tableName">表名</param>
        /// <param name="sqlBuilder">sql语句</param>
        /// <param name="visitor"></param>
        /// <returns></returns>
        private static async Task<CustomResult> ExecuteUpdate<TType>(IDbConnection dbConnection, TType model,
            string tableName, StringBuilder sqlBuilder, SqlExpressionVisitor visitor)
        {
            var operateInfo = GetOrmOperateCache<TType>(dbConnection.ConnectionString, sqlBuilder.ToString(), tableName,
                visitor.Properties.Select(e => e.Value));
            var parameterDictronaries = operateInfo.ParameterFunc?.Invoke(model) ?? new Dictionary<string, object>();

            foreach (var visitorParameter in visitor.Parameters)
            {
                parameterDictronaries.Add(visitorParameter.Key, visitorParameter.Value);
            }

            var row = await dbConnection.ExecuteAsync(operateInfo.Sql, new DynamicParameters(parameterDictronaries));
            return row > 0 ? new CustomResult() : new CustomResult(ResultType.UpdateFail, "更新失败");
        }

        /// <summary>
        /// 获取Orm框架缓存
        /// </summary>
        /// <typeparam name="TType">动态类型实体</typeparam>
        /// <param name="connectionString">链接字符串</param>
        /// <param name="sql">sql语句</param>
        /// <param name="tableName">表名</param>
        /// <param name="propertyInfoList">属性列表集合</param>
        /// <returns></returns>
        private static OrmOperateInfo GetOrmOperateCache<TType>(string connectionString, string sql, string tableName,
            IEnumerable<PropertyInfo> propertyInfoList)
        {
            var propertyInfos = propertyInfoList.ToList();
            if (!propertyInfos.Any()) return new OrmOperateInfo() { Sql = sql, ParameterFunc = null };

            var key = $"{tableName}|{sql}|{connectionString}";
            var cacheInfo = OrmCacheUtil.GetCacheInfo(key);
            if (cacheInfo != null) return cacheInfo;

            cacheInfo = new OrmOperateInfo
            {
                Sql = sql,
                ParameterFunc = SqlParameterEmit.CreateDictionaryDeleMethod<TType>(propertyInfos)
            };
            OrmCacheUtil.AddCacheInfo(key, cacheInfo);

            return cacheInfo;
        }

        /// <summary>
        /// 获取更新动态sql语句
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="update">更新动态字段</param>
        /// <param name="where">更新动态条件</param>
        /// <param name="tableName">表名</param>
        /// <param name="visitor"></param>
        /// <param name="sql">sql语句</param>
        private static void GetUpdateExpressionSql<TType>(Expression<Func<TType, object>> update,
            Expression<Func<TType, bool>> where, string tableName, SqlExpressionVisitor visitor, StringBuilder sql)
        {
            sql.Append("UPDATE ").Append(tableName).Append(" SET ");

            var updateFlag = new SqlVistorFlag(SqlVistorType.Update);
            visitor.Visit(update, updateFlag);

            sql.Append(updateFlag.Sql);
            sql.Append(" ").Append(VisitWhereExpress(visitor, where));
        }

        /// <summary>
        /// 处理where条件表达式，如果表达式为空，默认使用Id
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="visitor"></param>
        /// <param name="where">where条件</param>
        /// <returns></returns>
        private static string VisitWhereExpress<TType>(SqlExpressionVisitor visitor,
            Expression<Func<TType, bool>> where)
        {
            if (where != null)
            {
                var whereFlag = new SqlVistorFlag(SqlVistorType.Where);
                visitor.Visit(@where, whereFlag);
                return string.Concat(" WHERE ", whereFlag.Sql);
            }

            const string sql = " WHERE Id = @Id";
            if (visitor.Properties.ContainsKey("Id")) return sql;

            var propertyInfo = typeof(TType).GetProperty("Id");
            if (propertyInfo == null)
                throw new Exception("Update操作中where条件为空，且未发现Id属性");
            visitor.Properties.Add("Id", propertyInfo);
            return sql;
        }

        /// <summary>
        /// 获取单项扩展
        /// </summary>
        /// <typeparam name="TType">动态实体类型</typeparam>
        /// <param name="dbConnection">数据库连接</param>
        /// <param name="tableName">表名</param>
        /// <param name="model">实体类型</param>
        /// <param name="where">查询条件</param>
        /// <returns></returns>
        public static async Task<TType> Get<TType>(this IDbConnection dbConnection, string tableName = null,
            TType model = default(TType), Expression<Func<TType, bool>> where = null)
        {
            if (string.IsNullOrEmpty(tableName)) tableName = typeof(TType).Name;

            var sqlVisitor = new SqlExpressionVisitor();
            var whereSql = VisitWhereExpress(sqlVisitor, where);

            var sqlString = string.Concat("SELECT * FROM ", tableName, whereSql);

            var operateInfo = GetOrmOperateCache<TType>(dbConnection.ConnectionString, sqlString, tableName,
                sqlVisitor.Properties.Select(e => e.Value));
            var parameterDictronaries = operateInfo.ParameterFunc?.Invoke(model) ?? new Dictionary<string, object>();

            foreach (var sqlVisitorParameter in sqlVisitor.Parameters)
            {
                parameterDictronaries.Add(sqlVisitorParameter.Key, sqlVisitorParameter.Value);
            }

            return await dbConnection.QuerySingleAsync<TType>(operateInfo.Sql, parameterDictronaries);
        }
    }
}